function Form(id, action, method, fields)
{
	var self = this, $form = $('#'+id), name;
	
	this.id = id;
	this.fields = fields;
	
	this.action = action;
	this.method = method;
	
	for(name in fields)
	{
		$('#'+id+'_'+name).bind('keyup', {'name': name}, function(e)
		{
			if(e.keyCode !== 13)
			{
				self.validate(e.data.name, false);
			}
		});
	}
	
	$form.submit(function(e)
	{
		var valid = true;
		
		for(name in fields)
		{
			if(!self.validate(name, true)) valid = false;
		}
		
		if(!valid)
		{
			e.preventDefault();
		}
	});
}

/* VALIDATE */
Form.prototype.validate = function(name, custom)
{
	var field, $field, value, validator, $error, skip = false;
	
	$field = $('#'+this.id+'_'+name);
	$error = $('#'+this.id+'_'+name+'_error');
	$parent = $field.parent();
	
	error_output = $error[0].nodeName.toLowerCase();
	
	value = $field.val();
	
	// Optional
	if('optional' in this.fields[name].validation)
	{
		if(this.fields[name].validation.optional && value === '')
		{
			skip = true;
		}
	}
	
	// Validation
	if(!skip)
	{
		for(validator in this.fields[name].validation)
		{
			if(validator === 'optional') continue;
			
			if((validator in this && !this[validator](value, this.fields[name].validation[validator][1])) || (custom && !(validator in this) && !this.custom(name, validator, value)))
			{
				// Invalid
				switch(error_output)
				{
					case 'label':
						$error.html(this.fields[name].validation[validator][0]);
						break;
				}
				
				$parent.addClass('invalid');
				
				return false;
			}
		}
	}
	
	// Valid
	switch(error_output)
	{
		case 'label':
			$error.html($error.attr('title'));
			break;
	}
	
	$parent.removeClass('invalid');
	
	return true;
};

/* VALIDATORS */
Form.prototype.custom = function(name, validator, value)
{
	var name, data = {'custom': 1, 'name': name, 'validator': validator, 'value': value, 'fields': {}};
	
	data[this.id] = 1;
	
	for(name in this.fields)
	{
		data.fields[name] = $('#'+this.id+'_'+name).val();
	}
	
	return $.ajax(
	{
		'url': this.action,
		'type': this.method,
		'data': data,
		'dataType': 'html',
		'async': false
	}).responseText === '1';
};

Form.prototype.min_length = function(value, min)
{
	return value.length >= min;
};

Form.prototype.max_length = function(value, max)
{
	return value.length <= max;
};

Form.prototype.alphanumeric = function(value, custom)
{
	return !value.replace(new RegExp('['+custom+'a-z0-9_]', 'ig'), '').length;
};

Form.prototype.valid_email = function(value)
{
	return /^\S+@\S+\.\S+$/.test(value);
};

Form.prototype.confirm = function(value, field)
{
	return value === $('#'+this.id+'_'+field).val();
};